package ca.mcgill.model.digraph.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ca.mcgill.model.digraph.Digraph;
import ca.mcgill.model.digraph.OrdinaryEdge;
import ca.mcgill.model.functional.FiniteStateMachine;
import ca.mcgill.model.functional.State;
import ca.mcgill.model.functional.Transition;
import ca.mcgill.model.functional.triggering.Event;
import ca.mcgill.model.functional.triggering.ExternalEvent;
import ca.mcgill.model.implementation.ImplementationModel;
import ca.mcgill.model.implementation.Task;
import ca.mcgill.model.implementation.Transaction;

/**
 * This class provides the methods for the generation of the digraph (assuming
 * one external event) employing some strategies optimizing the runtimes of
 * these procedures, inter-alia caching.
 * 
 * @author EW228618
 * 
 */
public class OptimizedDigraphGeneration {

	private PreprocessingUtils pUtils;

	Map<Transition, Double> helper = new HashMap<Transition, Double>();
	Map<Transition, Map<Transition, Double>> casher = new HashMap<Transition, Map<Transition, Double>>();

	public OptimizedDigraphGeneration(PreprocessingUtils pUtils) {
		this.pUtils = pUtils;
	}

	public Map<Task, Digraph> generateDigraphs(ImplementationModel implModel) {

		establishTransitionToTransactionMapping(implModel);

		Map<Task, Digraph> result = new HashMap<Task, Digraph>();

		FiniteStateMachine fsm;

		double iAT = 0;

		for (Task task : implModel.getTasks()) {

			fsm = task.getFsm();

			Digraph digraph = new Digraph(task);

			for (State state : fsm.getStates()) {

				for (Transition inTrans : state.getInTransition()) {

					for (Transition outTrans : state.getOutTransitions()) {

						// This condition checks if currently considered FSM,
						// this is an
						// initial FSM in the chain - its transitions are
						// triggered by the
						// external events.
						if (inTrans.getTrigE() instanceof ExternalEvent) {

							if (inTrans.getTrigE() == outTrans.getTrigE()) {
								iAT = ((ExternalEvent) inTrans.getTrigE())
										.getPeriod();
							} else {
								iAT = 0;
							}

						} else {

							List<Transition> fPath = new ArrayList<Transition>();
							// fPath.add(inTrans);

							List<Transition> bPath = new ArrayList<Transition>();
							// bPath.add(outTrans);

							iAT = computeInterArrivalTime(inTrans.getTrigE(),
									outTrans.getTrigE(), fPath, bPath);
							// + wFSum(inTrans.getOutE());

						}

						OrdinaryEdge edge = new OrdinaryEdge(iAT, inTrans,
								outTrans);
						digraph.addEdge(edge);
						digraph.addVertex(inTrans);
						digraph.addVertex(outTrans);

					}

				}

			}

			result.put(task, digraph);
		}

		/*
		 * Call to the method that will set the deadlines for the transitions,
		 * i.e. digraph vertexes.
		 */
		setVertexDeadlines();

		return result;
	}

	private double computeInterArrivalTime(Event ev_i, Event ev_j,
			List<Transition> fPath, List<Transition> bPath) {

		double it = Double.MAX_VALUE;

		List<List<Transition>> trPaths = getPaths(ev_i, ev_j);

		for (List<Transition> path : trPaths) {

			double tBS = 0;
			List<Transition> nFPath = new ArrayList<Transition>();
			List<Transition> nBPath = new ArrayList<Transition>();

			for (int i = 0; i < path.size() - 1; i++) {

				nFPath.add(path.get(i));
				nBPath.add(path.get(i + 1));

				if (i == 0) {
					nFPath.addAll(fPath);
				}

				if (i == path.size() - 2) {
					nBPath.addAll(bPath);
				}

				// This means that we are dealing here with an initial FSM
				if (path.get(i).getTrigE() instanceof ExternalEvent) {

					for (Transition pathTrans : nFPath) {
						tBS -= pathTrans.getAction().getWCET();
					}

					for (Transition pathTrans : nBPath) {
						tBS += pathTrans.getAction().getBCET();
					}

					tBS += ((ExternalEvent) path.get(i).getTrigE()).getPeriod();

				} else {

					if (nFPath.size() == 1
							&& nBPath.size() == 1
							&& casher.get(nFPath.get(0)) != null
							&& casher.get(nFPath.get(0)).get(nBPath.get(0)) != null) {

						tBS = casher.get(nFPath.get(0)).get(nBPath.get(0));

					} else {
						tBS = computeInterArrivalTime(path.get(i).getTrigE(),
								path.get(i + 1).getTrigE(), nFPath, nBPath);

						if (nFPath.size() == 1 && nBPath.size() == 1) {
							Map<Transition, Double> pair = casher.get(nFPath
									.get(0));

							if (pair == null) {
								pair = new HashMap<Transition, Double>();
							}
							pair.put(nBPath.get(0), tBS);
							casher.put(nFPath.get(0), pair);
						}

					}

				}

			}

			if (tBS < it) {
				it = tBS;
			}

		}

		return it;
	}

	private List<List<Transition>> getPaths(Event ei, Event ej) {

		List<List<Transition>> result = new ArrayList<List<Transition>>();

		for (Transition srcTrans : this.pUtils.getEventsSrcTransitions()
				.get(ei)) {

			List<Transition> visited = new ArrayList<Transition>();
			visited.add(srcTrans);
			List<List<Transition>> res = pathCrawling(null, srcTrans, ej,
					visited);

			if (res != null) {
				result.addAll(res);
			}

		}

		if (result.size() == 0) {
			System.out.println("There is no path generating first event "
					+ ei.getName() + " and then " + ej.getName());
		}

		return result;

	}

	private List<List<Transition>> pathCrawling(Transition trans,
			Transition startTrans, Event endEv, List<Transition> visitedTrans) {

		List<List<Transition>> result = null;

		if (trans != null && trans.getOutE() == endEv) {
			result = new ArrayList<List<Transition>>();
			List<Transition> insideRes = new ArrayList<Transition>();
			insideRes.add(trans);
			result.add(insideRes);
			return result;
		} else if (trans != null && trans.getOutE() == startTrans.getOutE()) {
			return null;
		}

		if (trans == null) {
			trans = startTrans;

		}

		List<Transition> visitedCopy;

		List<List<Transition>> intermediateRes;
		for (Transition nextTrans : trans.getDstState().getOutTransitions()) {

			if (visitedTrans.contains(nextTrans) && nextTrans != startTrans) {
				continue;
			}

			visitedCopy = new ArrayList<Transition>(visitedTrans);
			visitedCopy.add(nextTrans);

			intermediateRes = pathCrawling(nextTrans, startTrans, endEv,
					visitedCopy);

			if (intermediateRes != null) {

				if (result == null) {
					result = new ArrayList<List<Transition>>();
				}

				List<Transition> intermediateList = null;
				for (List<Transition> list : intermediateRes) {
					intermediateList = new ArrayList<Transition>(list);
					intermediateList.add(0, trans);

					result.add(intermediateList);
				}
			}

		}

		return result;

	}

	private void setVertexDeadlines() {

		for (Transition transition : this.transactionToTransitions.keySet()) {

			transition.setRelativeDeadline(this.transactionToTransitions.get(
					transition).getDeadline());

		}

	}

	public Transaction getTransactionOfTransition(Transition transition) {
		return this.transactionToTransitions.get(transition);
	}

	private void establishTransitionToTransactionMapping(
			ImplementationModel implModel) {

		for (Transaction transaction : implModel.getTransactions()) {

			for (Task task : transaction.getTasksInTransaction()) {

				for (Transition transition : task.getFsm().getTransitions()) {

					transactionToTransitions.put(transition, transaction);

				}

			}

		}

	}

	private Map<Transition, Transaction> transactionToTransitions = new HashMap<Transition, Transaction>();
}
